package com.xltt.watchclient.module.service;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;

import com.xltt.watchclient.LogTool;
import com.xltt.watchclient.model.Message;
import com.xltt.watchclient.model.NotificationEntity;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;

import androidx.lifecycle.MutableLiveData;

import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
import static com.xltt.watchclient.Constants.MESSAGE_UUID;
import static com.xltt.watchclient.Constants.SERVICE_UUID;
import static com.xltt.watchclient.module.Consts.CLIENT_CONNECT;
import static com.xltt.watchclient.module.Consts.CLIENT_CONNECTED;
import static com.xltt.watchclient.module.Consts.CLIENT_DISCONNECT;
import static com.xltt.watchclient.module.Consts.CLIENT_START;

public class ConnectServer {
    DeviceService mContext;
    BluetoothManager mBluetoothManager;
    BluetoothAdapter mBluetoothAdapter;
    BluetoothDevice server;
    MutableLiveData<Integer> stateLiveData;
    int _mtu;
    int state;

    public ConnectServer(DeviceService context) {
        this.mContext = context;
        mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
    }

    private void setState(int state) {
        this.state = state;
        if (mContext.mBleController != null) {
            mContext.mBleController.notifyConnectState(this.state);
        }
    }

    BluetoothGatt connectGatt;

    public boolean connectToClient(String serverAddr) {
        server = mBluetoothAdapter.getRemoteDevice(serverAddr);
        if (server == null) {
            LogTool.d("ERR::client is null");
            return false;
        }

        if (connectGatt != null) {
            connectGatt.disconnect();
            connectGatt.close();
            connectGatt = null;
        }

        setState(CLIENT_START);
        connectGatt = server.connectGatt(mContext, false, new GattClientCallback(), TRANSPORT_LE);
        return true;
    }

    public String getConnectedAddress() {
        if (state == CLIENT_CONNECTED) {
            return connectGatt.getDevice().getName();
        }
        return "";
    }


    public boolean activeDevice(String name, String pwd) {
        if (connectGatt != null && messageCharacteristic != null) {
            return sendMessage(new Message(1, name, pwd).toJson());
        }
        return false;
    }

    public void send(NotificationEntity msg) {
        if (connectGatt != null && messageCharacteristic != null) {
            sendMessage(new Message((byte) 2, msg).toJson());
        }
    }

    public void sayHello() {
        if (connectGatt != null && messageCharacteristic != null) {
            messageCharacteristic.setValue(new byte[]{0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12});
            messageCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
            connectGatt.writeCharacteristic(messageCharacteristic);
        }
    }

    ConcurrentLinkedQueue<ByteBuffer> outData = new ConcurrentLinkedQueue();

    private synchronized boolean sendMessage(String json) {
        if (connectGatt != null && messageCharacteristic != null) {
            boolean needSend = outData.size() == 0;
            ByteBuffer nByteBuffer = ByteBuffer.allocate(5024);
            nByteBuffer.put(new byte[]{0x12, 0x12, 0x12, 0x12, 0x12});
            try {
                byte[] sendBt = json.getBytes("utf-8");
                nByteBuffer.putInt(sendBt.length);
                nByteBuffer.put(sendBt);
                nByteBuffer.flip();
                outData.add(nByteBuffer);

                if (needSend) {
                    ByteBuffer bt = outData.peek();
                    if (bt.remaining() == 0) {
                        outData.poll();
                        bt = outData.peek();
                        if (bt == null) {
                            return false;
                        }
                    }
                    byte[] bytes;
                    if (bt.remaining() > _mtu) {
                        bytes = new byte[_mtu];
                    } else {
                        bytes = new byte[bt.remaining()];
                    }
                    bt.get(bytes);
                    messageCharacteristic.setValue(bytes);
                    messageCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                    connectGatt.writeCharacteristic(messageCharacteristic);

                    if (bt.remaining() == 0) {
                        outData.poll();
                    }
                }
                return true;
            } catch (Exception e) {
                LogTool.e(e);
            }
        }
        return false;
    }

    private class GattClientCallback extends BluetoothGattCallback {
        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                LogTool.d("" + mtu);
                _mtu = mtu;
            }
            gatt.discoverServices();
        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            boolean isSuccess = status == BluetoothGatt.GATT_SUCCESS;
            boolean isConnected = newState == BluetoothProfile.STATE_CONNECTED;
            LogTool.d("onConnectionStateChange: Client " + gatt + "  success: " + isSuccess + " connected: " + isConnected);
            // try to send a message to the other device as a test
            if (isSuccess && isConnected) {
                if (gatt.getDevice().getBondState() == BOND_BONDED) {
                    unpairDevice(gatt.getDevice());
                }
                _mtu = 20;
                setState(CLIENT_CONNECT);
                connectGatt.requestConnectionPriority(1);
                connectGatt.requestMtu(500);
            } else {
                setState(CLIENT_DISCONNECT);
                outData.clear();
                gatt.disconnect();
                gatt.close();
            }
        }

        private void unpairDevice(BluetoothDevice device) {
            try {
                LogTool.d("unpairDevice");
                Method m = device.getClass()
                        .getMethod("removeBond", (Class[]) null);
                m.invoke(device, (Object[]) null);
            } catch (Exception e) {
                LogTool.d(e.getMessage());
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt discoveredGatt, int status) {
            super.onServicesDiscovered(discoveredGatt, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                setState(CLIENT_CONNECTED);
                connectGatt = discoveredGatt;
                BluetoothGattService service = discoveredGatt.getService(SERVICE_UUID);
                messageCharacteristic = service.getCharacteristic(MESSAGE_UUID);
                LogTool.d("onServicesDiscovered: Have gatt " + service.getUuid() + "," + messageCharacteristic.getUuid());
                sayHello();
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                ByteBuffer nByteBuffer = outData.peek();
                if (nByteBuffer != null && nByteBuffer.remaining() == 0) {
                    outData.poll();
                    nByteBuffer = outData.peek();
                }

                if (nByteBuffer != null) {
                    byte[] bytes;
                    if (nByteBuffer.remaining() > _mtu) {
                        bytes = new byte[_mtu];
                    } else {
                        bytes = new byte[nByteBuffer.remaining()];
                    }
                    nByteBuffer.get(bytes);
                    if (nByteBuffer.remaining() == 0) {
                        outData.poll();
                    }
                    messageCharacteristic.setValue(bytes);
                    messageCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                    if (!connectGatt.writeCharacteristic(messageCharacteristic)) {
                        outData.clear();
                    }
                }
            } else {
                LogTool.d("");
                outData.clear();
            }
        }
    }

    BluetoothGattCharacteristic messageCharacteristic;
}
